package ctu.kfad.run.iterpreter;

import ctu.kfad.run.InstanceController;
import ctu.kfad.run.memory.IStackObjectProvider;
import ctu.kfad.run.stack.AStackObject;
import ctu.kfad.run.stack.IntegerStackObject;
import ctu.kfad.run.stack.ReferenceStackObject;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Stack;

public class NativeInterpreter {

    private final HashMap<NativeMethodDescriptor, String> repository = new HashMap<NativeMethodDescriptor, String>();

    private InstanceController instanceController;
    private IStackObjectProvider stackObjectProvider;

    {
        repository.put(
                new NativeMethodDescriptor(ctu.kfad.run.util.StringUtil.class.getName(), "concatenate"),
                ctu.kfad.run.iterpreter.StringUtil.class.getName());
        repository.put(
                new NativeMethodDescriptor(ctu.kfad.run.util.StringUtil.class.getName(), "print"),
                ctu.kfad.run.iterpreter.StringUtil.class.getName());
        repository.put(
                new NativeMethodDescriptor(ctu.kfad.run.util.StringUtil.class.getName(), "convertToInt"),
                ctu.kfad.run.iterpreter.StringUtil.class.getName());
        repository.put(
                new NativeMethodDescriptor(ctu.kfad.run.util.StringUtil.class.getName(), "convertFromInt"),
                ctu.kfad.run.iterpreter.StringUtil.class.getName());
        repository.put(
                new NativeMethodDescriptor(ctu.kfad.run.util.StringUtil.class.getName(), "split"),
                ctu.kfad.run.iterpreter.StringUtil.class.getName());
        repository.put(
                new NativeMethodDescriptor(ctu.kfad.run.util.FileUtil.class.getName(), "readToString"),
                ctu.kfad.run.iterpreter.FileUtil.class.getName());
        repository.put(
                new NativeMethodDescriptor(ctu.kfad.run.util.FileUtil.class.getName(), "writeTo"),
                ctu.kfad.run.iterpreter.FileUtil.class.getName());

    }

    public NativeInterpreter(InstanceController instanceController, IStackObjectProvider stackObjectProvider) {
        this.instanceController = instanceController;
        this.stackObjectProvider = stackObjectProvider;
    }

    public AStackObject interpret(JavaClass calledClass, Method method, Stack<AStackObject> arguments) {
        final String methodName = method.getName();

        final NativeMethodDescriptor nativeMethodDescriptor = new NativeMethodDescriptor(calledClass.getClassName(), methodName);

        final String nativeClass = repository.get(nativeMethodDescriptor);

        final LinkedList<Object> nativeArguments = new LinkedList<>();
        while (!arguments.isEmpty()) {
            final AStackObject argument = arguments.pop();
            if (argument.getType() == AStackObject.REFERENCE_STACK_OBJECT) {
                final String s = instanceController.loadClassName((ReferenceStackObject) argument);
                if (InstanceController.JAVA_STRING.equals(s)) {
                    final String string = instanceController.loadString((ReferenceStackObject) argument);
                    nativeArguments.add(string);
                }
            }
            if (argument.getType() == AStackObject.INTEGER_STACK_OBJECT) {
                nativeArguments.add(((IntegerStackObject) argument).getIntValue());
            }
        }

        try {
            final Class<?> aClass = NativeInterpreter.class.getClassLoader().loadClass(nativeClass);
            final java.lang.reflect.Method[] methods = aClass.getMethods();
            for (java.lang.reflect.Method method1 : methods) {
                if (methodName.equals(method1.getName())) {
                    try {
                        return toStackObject(method1.invoke(null, nativeArguments.toArray()));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    private AStackObject toStackObject(Object o) {
        if (o == null) {
            return null;
        }
        if (o instanceof String) {
            final ReferenceStackObject stringReferenceStackObject = instanceController.saveString((String) o, stackObjectProvider);
            return stringReferenceStackObject;
        }
        if (o instanceof String[]) {
            return instanceController.saveStringArray((String[]) o, stackObjectProvider);
        }
        if (o instanceof Integer) {
            return new IntegerStackObject((Integer) o);
        }
        throw new UnsupportedOperationException("UNKNOWN");
    }

    private static class NativeMethodDescriptor {

        private final String className;
        private final String methodName;

        private NativeMethodDescriptor(String className, String methodName) {
            this.className = className;
            this.methodName = methodName;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            NativeMethodDescriptor that = (NativeMethodDescriptor) o;

            if (!className.equals(that.className)) return false;
            if (!methodName.equals(that.methodName)) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = className.hashCode();
            result = 31 * result + methodName.hashCode();
            return result;
        }
    }

}
